package com.utils.aes;

import com.google.common.base.Strings;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.RandomStringUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * AES 工具类
 * https://www.liaoxuefeng.com/wiki/1252599548343744/1304227762667553
 *
 * @author 谢长春 2022-02-06
 */
@Slf4j
public final class Aes {

    /**
     * https://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/CryptoSpec.html#trans
     */
    private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
    /**
     * 参考文档: https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher
     * <pre>
     * Cipher (Encryption) Algorithms
     * Cipher Algorithm Names
     * The following names can be specified as the algorithm component in a transformation when requesting an instance of Cipher.
     *
     * <table border="5" cellpadding="5" frame="border" width="90%" summary="Cipher Algorithm Names">
     * <thead> <tr> <th>Algorithm Name</th> <th>Description</th> </tr> </thead>
     * <tbody>
     * <tr> <td>AES</td> <td>Advanced Encryption Standard as specified by NIST in <a href="http://csrc.nist.gov/publications/fips/index.html">FIPS 197</a>. Also known as the Rijndael algorithm by Joan Daemen and Vincent Rijmen, AES is a 128-bit block cipher supporting keys of 128, 192, and 256 bits.</td> </tr>
     * <tr> <td>AESWrap</td> <td>The AES key wrapping algorithm as described in <a href="http://www.ietf.org/rfc/rfc3394.txt">RFC 3394</a>.</td> </tr>
     * <tr> <td>ARCFOUR</td> <td>A stream cipher believed to be fully interoperable with the RC4 cipher developed by Ron Rivest. For more information, see K. Kaukonen and R. Thayer, "A Stream Cipher Encryption Algorithm 'Arcfour'", Internet Draft (expired), <a href="http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt"> draft-kaukonen-cipher-arcfour-03.txt</a>.</td> </tr>
     * <tr> <td>Blowfish</td> <td>The <a href="http://www.schneier.com/blowfish.html">Blowfish block cipher</a> designed by Bruce Schneier.</td> </tr>
     * <tr> <td>CCM</td> <td>Counter/CBC Mode, as defined in <a href="http://csrc.nist.gov/publications/nistpubs/800-38C/SP800-38C_updated-July20_2007.pdf">NIST Special Publication SP 800-38C</a>.</td> </tr>
     * <tr> <td>DES</td> <td>The Digital Encryption Standard as described in <a href="http://csrc.nist.gov/publications/fips/index.html">FIPS PUB 46-3</a>.</td> </tr>
     * <tr> <td>DESede</td> <td>Triple DES Encryption (also known as DES-EDE, 3DES, or Triple-DES). Data is encrypted using the DES algorithm three separate times. It is first encrypted using the first subkey, then decrypted with the second subkey, and encrypted with the third subkey.</td> </tr>
     * <tr> <td>DESedeWrap</td> <td>The DESede key wrapping algorithm as described in <a href="http://www.ietf.org/rfc/rfc3217.txt">RFC 3217</a> .</td> </tr>
     * <tr> <td>ECIES</td> <td>Elliptic Curve Integrated Encryption Scheme</td> </tr>
     * <tr> <td>GCM</td> <td>Galois/Counter Mode, as defined in <a href="http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf">NIST Special Publication SP 800-38D</a>.</td> </tr>
     * <tr> <td>PBEWith&lt;digest&gt;And&lt;encryption&gt; PBEWith&lt;prf&gt;And&lt;encryption&gt;</td> <td>The password-based encryption algorithm found in (PKCS5), using the specified message digest (&lt;digest&gt;) or pseudo-random function (&lt;prf&gt;) and encryption algorithm (&lt;encryption&gt;). Examples: <ul> <li><b>PBEWithMD5AndDES</b>: The password-based encryption algorithm as defined in <a href="http://www.rsa.com/rsalabs/node.asp?id=2127">RSA Laboratories, "PKCS #5: Password-Based Encryption Standard," version 1.5, Nov 1993</a>. Note that this algorithm implies <a href="#cbcMode"><i>CBC</i></a> as the cipher mode and <a href="#pkcs5Pad"><i>PKCS5Padding</i></a> as the padding scheme and cannot be used with any other cipher modes or padding schemes.</li> <li><b>PBEWithHmacSHA256AndAES_128</b>: The password-based encryption algorithm as defined in <a href="http://www.rsa.com/rsalabs/node.asp?id=2127">RSA Laboratories, "PKCS #5: Password-Based Cryptography Standard," version 2.0, March 1999</a>.</li> </ul> </td> </tr>
     * <tr> <td>RC2</td> <td>Variable-key-size encryption algorithms developed by Ron Rivest for RSA Data Security, Inc.</td> </tr>
     * <tr> <td>RC4</td> <td>Variable-key-size encryption algorithms developed by Ron Rivest for RSA Data Security, Inc. (See note prior for ARCFOUR.)</td> </tr>
     * <tr> <td>RC5</td> <td>Variable-key-size encryption algorithms developed by Ron Rivest for RSA Data Security, Inc.</td> </tr>
     * <tr> <td>RSA</td> <td>The RSA encryption algorithm as defined in <a href="http://www.rsa.com/rsalabs/node.asp?id=2125">PKCS #1</a></td> </tr>
     * </tbody>
     * </table>
     */
    private static final String CIPHER_ALGORITHM = "AES";
    /**
     *
     */
    private static final int IV_LENGTH = 16;
    private static final int ZERO = 0;
    /**
     * 固定 IV 。 设置固定 IV 之后相同内容每次加密结果一样
     */
    public static IvParameterSpec IV_PARAMETER;
    /**
     * 16 位 IV 规则， 默认全部随机，无规律
     */
    public static IvRandomRule IV_RANDOM_RULE = IvRandomRule.alphanumeric;

    /**
     * 密钥， 长度:32字节(256位)
     *
     * @alias RandomStringUtils.randomAlphanumeric(32)
     * @alias RandomStringUtils.randomAscii(32)
     */
    public static SecretKeySpec SECRET_KEY;

    private Aes() {

    }

    /**
     * 设置密钥
     *
     * @param secretKey {@link String} RandomStringUtils.randomAlphanumeric(32)
     */
    public static void setSecretKey(final String secretKey) {
        if (Strings.isNullOrEmpty(secretKey) || secretKey.length() != 32) {
            throw new IllegalArgumentException("secretKey 长度必须是 32 位");
        }
        SECRET_KEY = new SecretKeySpec(secretKey.getBytes(UTF_8), CIPHER_ALGORITHM);
    }

    /**
     * <pre>
     * 设置动态 IV 生成规则
     *   IvRandomRule.alphanumeric     : RandomStringUtils.randomAlphanumeric 工具类生成 16 位随机 【A-Za-z0-9】 的字符串，解密时不校验 IV 生成规则
     *   IvRandomRule.dateAlphanumeric : 10 位日期(yyyyMMddHH) + 6 位随机 【A-Za-z0-9】 的字符串，解密时提取前 10 位日期，检查有效期为 24 小时
     *   IvRandomRule.timestamp        : 13位时间戳，前面补0，解密检查时间戳，有效期为 24 小时
     * </pre>
     *
     * @param rule {@link IvRandomRule}
     */
    public static void setIvRandomRule(final IvRandomRule rule) {
        IV_RANDOM_RULE = rule;
    }

    /**
     * 设置 IV ， 长度必须是 16 位自负
     *
     * @param iv {@link String} RandomStringUtils.randomAlphanumeric(16)
     */
    @SneakyThrows
    public static void setIV(final String iv) {
        if (Objects.isNull(iv)) {
            IV_PARAMETER = null;
            return;
        }
        if (Strings.isNullOrEmpty(iv) || iv.length() != 16) {
            throw new IllegalArgumentException("secretKey 长度必须是 16 位");
        }
        IV_PARAMETER = new IvParameterSpec(iv.getBytes(UTF_8));
    }

    /**
     * <pre>
     * aes 加密， 有两种模式：
     * 动态IV： （默认），相同内容每次获得的密文不一样， 可自定义 IV 生成规则
     * 固定IV： 通过设置 {@link Aes#setIV(String)} ，固定 IV 之后相同内容每次获得的密文都一样
     * </pre>
     *
     * @param data {@link String} 明文
     * @return {@link String} 密文
     */
    @SneakyThrows
    public static String encrypt(final String data) {
        if (Strings.isNullOrEmpty(data)) {
            return null;
        }
        if (Objects.isNull(IV_PARAMETER)) { // 未指定 IV 使用动态 IV 加密
            // CBC模式需要生成一个 16 bytes 的 IV（initialization vector）。  IV不需要保密，把IV和密文一起返回，返回随机IV的好处是每次加密都能获得不同的密文
            // CBC模式需要生成一个16 bytes的initialization vector:
            // final SecureRandom sr = SecureRandom.getInstanceStrong();
            // final byte[] ivBytes = sr.generateSeed(16);


            final byte[] ivBytes = IV_RANDOM_RULE.get();
            final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            // 初始化加密参数，设置为加密模式，指定密钥，设置IV
            cipher.init(Cipher.ENCRYPT_MODE, SECRET_KEY, new IvParameterSpec(ivBytes));
            final byte[] dataBytes = cipher.doFinal(data.getBytes(UTF_8));
            final byte[] bytes = new byte[IV_LENGTH + dataBytes.length];
            System.arraycopy(ivBytes, ZERO, bytes, ZERO, IV_LENGTH);
            System.arraycopy(dataBytes, ZERO, bytes, IV_LENGTH, dataBytes.length);
//            final String result = org.apache.commons.codec.binary.Hex.encodeHexString(bytes);
            final String result = org.apache.commons.codec.binary.Base64.encodeBase64String(bytes);
            if (log.isDebugEnabled()) {
                log.debug("{} => {}", data, result);
            }
            return result;
        }
        // 使用固定 IV 加密
        final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, SECRET_KEY, IV_PARAMETER);
//        final String result = org.apache.commons.codec.binary.Hex.encodeHexString(cipher.doFinal(data.getBytes(UTF_8)));
        final String result = org.apache.commons.codec.binary.Base64.encodeBase64String(cipher.doFinal(data.getBytes(UTF_8)));
        if (log.isDebugEnabled()) {
            log.debug("{} => {}", data, result);
        }
        return result;
    }

    /**
     * <pre>
     * aes 加密， 有两种模式：
     * 动态IV： （默认），相同内容每次获得的密文不一样， 可自定义 IV 生成规则
     * 固定IV： 通过设置 {@link Aes#setIV(String)} ，固定 IV 之后相同内容每次获得的密文都一样
     * </pre>
     *
     * @param list {@link Collection<String>} 明文
     * @return {@link String} 密文
     */
    @SneakyThrows
    public static List<String> encrypt(final Collection<String> list) {
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        if (Objects.isNull(IV_PARAMETER)) { // 未指定 iv 使用动态 iv 加密
            final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            return list.stream()
                    .map(data -> {
                        try {
                            // CBC模式需要生成一个 16 bytes 的 IV（initialization vector）。  IV不需要保密，把IV和密文一起返回，返回随机IV的好处是每次加密都能获得不同的密文
                            final byte[] ivBytes = IV_RANDOM_RULE.get();
                            cipher.init(Cipher.ENCRYPT_MODE, SECRET_KEY, new IvParameterSpec(ivBytes));

                            final byte[] dataBytes = cipher.doFinal(data.getBytes(UTF_8));
                            final byte[] bytes = new byte[IV_LENGTH + dataBytes.length];
                            System.arraycopy(ivBytes, ZERO, bytes, ZERO, IV_LENGTH);
                            System.arraycopy(dataBytes, ZERO, bytes, IV_LENGTH, dataBytes.length);
                            // final String result = org.apache.commons.codec.binary.Hex.encodeHexString(bytes);
                            final String result = org.apache.commons.codec.binary.Base64.encodeBase64String(bytes);
                            if (log.isDebugEnabled()) {
                                log.debug("{} => {}", data, result);
                            }
                            return result;
                        } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
                            throw new IllegalArgumentException(String.format("加密失败:%s", data), e);
                        }
                    })
                    .collect(Collectors.toList());
        }
        // 使用固定的 IV 加密
        final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, SECRET_KEY, IV_PARAMETER);
        return list.stream()
                .map(data -> {
                    try {
                        // final String result = org.apache.commons.codec.binary.Hex.encodeHexString(cipher.doFinal(data.getBytes(UTF_8)));
                        final String result = org.apache.commons.codec.binary.Base64.encodeBase64String(cipher.doFinal(data.getBytes(UTF_8)));
                        if (log.isDebugEnabled()) {
                            log.debug("{} => {}", data, result);
                        }
                        return result;
                    } catch (IllegalBlockSizeException | BadPaddingException e) {
                        throw new IllegalArgumentException(String.format("加密失败:%s", data), e);
                    }
                })
                .collect(Collectors.toList());
    }

    /**
     * 解密，如果是动态 IV，则会校验 IV 规则是否正确，不正确会抛出异常
     *
     * @param data {@link String} 密文
     * @return 明文
     */
    @SneakyThrows
    public static String decrypt(final String data) {
        if (Strings.isNullOrEmpty(data)) {
            return null;
        }
        if (Objects.isNull(IV_PARAMETER)) { // 未指定 iv 使用动态 iv 解密
            // 将 data 分割成 IV 和密文
            // final byte[] bytes = org.apache.commons.codec.binary.Hex.decodeHex(data);
            final byte[] bytes = org.apache.commons.codec.binary.Base64.decodeBase64(data);
            final byte[] ivBytes = new byte[IV_LENGTH];
            final byte[] dataBytes = new byte[bytes.length - IV_LENGTH];
            System.arraycopy(bytes, ZERO, ivBytes, ZERO, IV_LENGTH);
            IV_RANDOM_RULE.assertIV(ivBytes); // 断言 IV 规则是否匹配
            System.arraycopy(bytes, IV_LENGTH, dataBytes, ZERO, dataBytes.length);

            final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, SECRET_KEY, new IvParameterSpec(ivBytes));
            final String result = new String(cipher.doFinal(dataBytes));
            if (log.isDebugEnabled()) {
                log.debug("{}({}) => {}", data, new String(ivBytes, UTF_8), result);
            }
            return result;
        }
        // 使用固定的 IV 解密
        final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, SECRET_KEY, IV_PARAMETER);
        // final String result = new String(cipher.doFinal(org.apache.commons.codec.binary.Hex.decodeHex(data)));
        final String result = new String(cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(data)));
        if (log.isDebugEnabled()) {
            log.debug("{} => {}", data, result);
        }
        return result;
    }

    /**
     * 解密，如果是动态 IV，则会校验 IV 规则是否正确，不正确会抛出异常
     *
     * @param list {@link Collection<String>} 密文
     * @return 明文
     */
    @SneakyThrows
    public static List<String> decrypt(final Collection<String> list) {
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        if (Objects.isNull(IV_PARAMETER)) { // 未指定 iv 使用动态 iv 解密
            final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            final byte[] ivBytes = new byte[IV_LENGTH];
            return list.stream()
                    .map(data -> {
                        try {
                            // 将 data 分割成 IV 和密文
                            // final byte[] bytes = org.apache.commons.codec.binary.Hex.decodeHex(data);
                            final byte[] bytes = org.apache.commons.codec.binary.Base64.decodeBase64(data);
                            System.arraycopy(bytes, ZERO, ivBytes, ZERO, IV_LENGTH);
                            IV_RANDOM_RULE.assertIV(ivBytes); // 断言 IV 规则是否匹配
                            cipher.init(Cipher.DECRYPT_MODE, SECRET_KEY, new IvParameterSpec(ivBytes));

                            final byte[] dataBytes = new byte[bytes.length - IV_LENGTH];
                            System.arraycopy(bytes, IV_LENGTH, dataBytes, ZERO, dataBytes.length);
                            final String result = new String(cipher.doFinal(dataBytes));
                            if (log.isDebugEnabled()) {
                                log.debug("{} => {}", data, result);
                            }
                            return result;
                        } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
                            throw new IllegalArgumentException(String.format("解密失败:%s", data), e);
                        }
                    })
                    .collect(Collectors.toList());

        }
        // 使用固定的 IV 解密
        final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, SECRET_KEY, IV_PARAMETER);
        return list.stream()
                .map(data -> {
                    try {
                        // final String result = new String(cipher.doFinal(org.apache.commons.codec.binary.Hex.decodeHex(data)));
                        final String result = new String(cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(data)));
                        if (log.isDebugEnabled()) {
                            log.debug("{} => {}", data, result);
                        }
                        return result;
                    } catch (IllegalBlockSizeException | BadPaddingException e) {
                        throw new IllegalArgumentException(String.format("解密失败:%s", data), e);
                    }
                })
                .collect(Collectors.toList());
    }

    @SneakyThrows
    public static void main(String[] args) {
        final String secretKey = "VnlSwQROeLeaZyXYqKFzdWmtsYseGwEF";
        Aes.setSecretKey(secretKey);

        // 动态 IV
        {
            final String planText = "java 加密";
            log.info("动态 IV: 明文：{} => 加密：{}", planText, Aes.encrypt(planText));
            log.info("动态 IV: 明文：{} => 加密：{}", planText, Aes.encrypt(planText));

            String encryptText = Aes.encrypt("java 加密");
            log.info("动态 IV: 明文：{} => 加密：{} => 解密：{}", planText, encryptText, Aes.decrypt(encryptText));
            encryptText = Aes.encrypt("java 加密");
            log.info("动态 IV: 明文：{} => 加密：{} => 解密：{}", planText, encryptText, Aes.decrypt(encryptText));
        }

//        for (int i = 0; i < 100; i++) {
//            System.out.println(Aes.encrypt(Util.uuid32()));
//        }

        // 动态 IV， 日期校验
        Aes.setIvRandomRule(IvRandomRule.dateAlphanumeric);
        {
            final String planText = "java 加密";
            log.info("动态 IV 日期校验: 明文：{} => 加密：{}", planText, Aes.encrypt(planText));
            log.info("动态 IV 日期校验: 明文：{} => 加密：{}", planText, Aes.encrypt(planText));

            String encryptText = Aes.encrypt("java 加密");
            log.info("动态 IV 日期校验: 明文：{} => 加密：{} => 解密：{}", planText, encryptText, Aes.decrypt(encryptText));
            encryptText = Aes.encrypt("java 加密");
            log.info("动态 IV 日期校验: 明文：{} => 加密：{} => 解密：{}", planText, encryptText, Aes.decrypt(encryptText));

            try {
                log.info("测试解析失败：{}", Aes.decrypt("MjAyMjAyMjExOVhLMERIThj+Zs4M97pi7XPDrw90NK4="));
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
            try {
                log.info("测试解析失败：{}", Aes.decrypt("MjAyMjAyMjExOWpuSDY4dYIRKmCpNgk5tDBEZW++CYQ="));
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
            try {
                log.info("测试解析失败：{}", Aes.decrypt("MjAyMjAyMjExOXI0NGFqT878rpxRIEM3BNp3uwXjqZ4="));
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
            try {
                log.info("测试解析失败：{}", Aes.decrypt("MjAyMjAyMjExOUh1N3JrV0SGpkyGa/uFWjzhhw7ySPc="));
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }

        // 动态 IV， 时间戳校验
        Aes.setIvRandomRule(IvRandomRule.timestamp);
        {
            final String planText = "java 加密";
            log.info("动态 IV 时间戳校验: 明文：{} => 加密：{}", planText, Aes.encrypt(planText));
            log.info("动态 IV 时间戳校验: 明文：{} => 加密：{}", planText, Aes.encrypt(planText));

            String encryptText = Aes.encrypt("java 加密");
            log.info("动态 IV 时间戳校验: 明文：{} => 加密：{} => 解密：{}", planText, encryptText, Aes.decrypt(encryptText));
            encryptText = Aes.encrypt("java 加密");
            log.info("动态 IV 时间戳校验: 明文：{} => 加密：{} => 解密：{}", planText, encryptText, Aes.decrypt(encryptText));

            try {
                log.info("测试解析失败：{}", Aes.decrypt("MDAwMTY0NTQ0NDY2NzYwMSi5UDxSmmaai6Sph4TSx3Q="));
            } catch (Exception e) {
                log.error(e.getMessage());
            }
            try {
                log.info("测试解析失败：{}", Aes.decrypt("MDAwMTY0NTQ0NDY2NzYwMnM4lWT6TtYn+SIa1klchlI="));
            } catch (Exception e) {
                log.error(e.getMessage());
            }
            try {
                log.info("测试解析失败：{}", Aes.decrypt("MDAwMTY0NTQ0NDY2NzYwMnM4lWT6TtYn+SIa1klchlI="));
            } catch (Exception e) {
                log.error(e.getMessage());
            }
            try {
                log.info("测试解析失败：{}", Aes.decrypt("MDAwMTY0NTQ0NDY2NzYwMnM4lWT6TtYn+SIa1klchlI="));
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
        final String iv = RandomStringUtils.randomAlphanumeric(16);
        // 固定 IV
        Aes.setIV("2022010100112299");
        log.info("固定iv：{}", iv);
        {
            final String planText = "java 加密";
            log.info("固定 IV: 明文：{} => 加密：{}", planText, Aes.encrypt(planText));
            log.info("固定 IV: 明文：{} => 加密：{}", planText, Aes.encrypt(planText));

            String encryptText = Aes.encrypt("java 加密");
            log.info("固定 IV: 明文：{} => 加密：{} => 解密：{}", planText, encryptText, Aes.decrypt(encryptText));
            encryptText = Aes.encrypt("java 加密");
            log.info("固定 IV: 明文：{} => 加密：{} => 解密：{}", planText, encryptText, Aes.decrypt(encryptText));
        }

        {
            setIV("2022010100112299");
            log.info("js 静态IV密文解密 => {}", Aes.decrypt("/IcOIjPiIvm3oKbNISzwdonBPfcHE4vxUiP22KTXdlU="));
        }
        {
            setIV(null);
            setIvRandomRule(IvRandomRule.alphanumeric);
            log.info("js 动态IV密文解密 => {}", Aes.decrypt("MDAwMTY0NTQ0NDYyODY0OQOK1jx9D1vA+fy4VW7f2e8od4X3klGeQx67JB/uiIMC"));
        }
    }
}
